`include  "/home/zhoupu/ysyx-workbench/npc/vsrc/mduop.v"

module IDU(
  input clk,
  input [31:0] inst,
  output [4:0] rs1,
  output [4:0] rs2,
  output [4:0] rd,   
  output [63:0] imm,
  output [3:0] ALUop,
  output [3:0] MDUop,
  output [2:0] ALUAsrc,
  output [2:0] ALUBsrc,
  output [2:0] Branch,
  output RegWen,
  output [1:0] rf_write_src_out,
  output MemWen,
  output MemRen,
  output [2:0] MemOp
);

  import "DPI-C" function void ebreak();
  wire [6:0] opcode, funct7;
  wire [2:0] funct3;
  wire [63:0] immI, immS, immB, immU, immJ;

  assign opcode = inst[6:0];
  assign funct3 = inst[14:12];
  assign funct7 = inst[31:25];
  assign rs1  = inst[19:15];
  assign rs2  = inst[24:20];
  assign rd   = inst[11:7];
  assign immI = {{52{inst[31]}}, inst[31:20]};
  assign immU = {{32{inst[31]}}, inst[31:12], 12'b0};
  assign immB = {{52{inst[31]}}, inst[7], inst[30:25], inst[11:8], 1'b0};
  assign immJ = {{44{inst[31]}}, inst[19:12], inst[20], inst[30:21], 1'b0};
  assign immS = {{52{inst[31]}}, inst[31:25], inst[11:7]};

  wire LUI, AUIPC, JAL, JALR;
  wire BEQ, BNE, BGE, BLT, BGEU, BLTU;
  wire LB, LH, LW, LBU, LHU;
  wire SB, SH, SW;
  wire ADDI, SLTI, SLTIU, XORI, ORI, ANDI, SLLI, SRLI, SRAI;
  wire ADD, SUB, SLL, SLT, SLTU, XOR, SRL, SRA, OR, AND;
  wire EBREAK;
  wire LWU, LD, SD;
  wire ADDIW, SLLIW, SRLIW, SRAIW;
  wire ADDW, SUBW, SLLW, SRLW, SRAW;
  wire MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU;
  wire MULW, DIVW, DIVUW, REMW, REMUW;

  assign LUI    = (opcode == 7'b0110111);  
  assign AUIPC  = (opcode == 7'b0010111);
  assign JAL    = (opcode == 7'b1101111);
  assign JALR   = (opcode == 7'b1100111) & (funct3 == 3'b000);
  assign BEQ    = (opcode == 7'b1100011) & (funct3 == 3'b000);
  assign BNE    = (opcode == 7'b1100011) & (funct3 == 3'b001);
  assign BLT    = (opcode == 7'b1100011) & (funct3 == 3'b100);
  assign BGE    = (opcode == 7'b1100011) & (funct3 == 3'b101);
  assign BLTU   = (opcode == 7'b1100011) & (funct3 == 3'b110);
  assign BGEU   = (opcode == 7'b1100011) & (funct3 == 3'b111);
  assign LB     = (opcode == 7'b0000011) & (funct3 == 3'b000);
  assign LH     = (opcode == 7'b0000011) & (funct3 == 3'b001);
  assign LW     = (opcode == 7'b0000011) & (funct3 == 3'b010);
  assign LBU    = (opcode == 7'b0000011) & (funct3 == 3'b100);
  assign LHU    = (opcode == 7'b0000011) & (funct3 == 3'b101);
  assign SB     = (opcode == 7'b0100011) & (funct3 == 3'b000);
  assign SH     = (opcode == 7'b0100011) & (funct3 == 3'b001);
  assign SW     = (opcode == 7'b0100011) & (funct3 == 3'b010);
  assign ADDI   = (opcode == 7'b0010011) & (funct3 == 3'b000);
  assign SLTI   = (opcode == 7'b0010011) & (funct3 == 3'b010);
  assign SLTIU  = (opcode == 7'b0010011) & (funct3 == 3'b011);
  assign XORI   = (opcode == 7'b0010011) & (funct3 == 3'b100);
  assign ORI    = (opcode == 7'b0010011) & (funct3 == 3'b110);
  assign ANDI   = (opcode == 7'b0010011) & (funct3 == 3'b111);
  assign SLLI   = (opcode == 7'b0010011) & (funct3 == 3'b001) & (funct7[6:1] == 6'b000000);
  assign SRLI   = (opcode == 7'b0010011) & (funct3 == 3'b101) & (funct7[6:1] == 6'b000000);
  assign SRAI   = (opcode == 7'b0010011) & (funct3 == 3'b101) & (funct7[6:1] == 6'b010000);
  assign ADD    = (opcode == 7'b0110011) & (funct3 == 3'b000) & (funct7 == 7'b0000000);
  assign SUB    = (opcode == 7'b0110011) & (funct3 == 3'b000) & (funct7 == 7'b0100000);
  assign SLL    = (opcode == 7'b0110011) & (funct3 == 3'b001) & (funct7 == 7'b0000000);
  assign SLT    = (opcode == 7'b0110011) & (funct3 == 3'b010) & (funct7 == 7'b0000000);
  assign SLTU   = (opcode == 7'b0110011) & (funct3 == 3'b011) & (funct7 == 7'b0000000);
  assign XOR    = (opcode == 7'b0110011) & (funct3 == 3'b100) & (funct7 == 7'b0000000);
  assign SRL    = (opcode == 7'b0110011) & (funct3 == 3'b101) & (funct7 == 7'b0000000);
  assign SRA    = (opcode == 7'b0110011) & (funct3 == 3'b101) & (funct7 == 7'b0100000);
  assign OR     = (opcode == 7'b0110011) & (funct3 == 3'b110) & (funct7 == 7'b0000000);
  assign AND    = (opcode == 7'b0110011) & (funct3 == 3'b111) & (funct7 == 7'b0000000);

  assign EBREAK = (opcode == 7'b1110011) & (funct3 == 3'b000) & (inst[31:20] == 12'b1);

  assign LWU    = (opcode == 7'b0000011) & (funct3 == 3'b110);
  assign LD     = (opcode == 7'b0000011) & (funct3 == 3'b011);
  assign SD     = (opcode == 7'b0100011) & (funct3 == 3'b011);

  assign ADDIW  = (opcode == 7'b0011011) & (funct3 == 3'b000);
  assign SLLIW  = (opcode == 7'b0011011) & (funct3 == 3'b001) & (funct7 == 7'b0000000);
  assign SRLIW  = (opcode == 7'b0011011) & (funct3 == 3'b101) & (funct7 == 7'b0000000);
  assign SRAIW  = (opcode == 7'b0011011) & (funct3 == 3'b101) & (funct7 == 7'b0100000);
  
  assign ADDW   = (opcode == 7'b0111011) & (funct3 == 3'b000) & (funct7 == 7'b0000000);
  assign SUBW   = (opcode == 7'b0111011) & (funct3 == 3'b000) & (funct7 == 7'b0100000);
  assign SLLW   = (opcode == 7'b0111011) & (funct3 == 3'b001) & (funct7 == 7'b0000000);
  assign SRLW   = (opcode == 7'b0111011) & (funct3 == 3'b101) & (funct7 == 7'b0000000);
  assign SRAW   = (opcode == 7'b0111011) & (funct3 == 3'b101) & (funct7 == 7'b0100000);

  assign MUL    = (opcode == 7'b0110011) & (funct3 == 3'b000) & (funct7 == 7'b0000001);
  assign MULH   = (opcode == 7'b0110011) & (funct3 == 3'b001) & (funct7 == 7'b0000001);
  assign MULHSU = (opcode == 7'b0110011) & (funct3 == 3'b010) & (funct7 == 7'b0000001);
  assign MULHU  = (opcode == 7'b0110011) & (funct3 == 3'b011) & (funct7 == 7'b0000001);
  assign DIV    = (opcode == 7'b0110011) & (funct3 == 3'b100) & (funct7 == 7'b0000001);
  assign DIVU   = (opcode == 7'b0110011) & (funct3 == 3'b101) & (funct7 == 7'b0000001);
  assign REM    = (opcode == 7'b0110011) & (funct3 == 3'b110) & (funct7 == 7'b0000001);
  assign REMU   = (opcode == 7'b0110011) & (funct3 == 3'b111) & (funct7 == 7'b0000001);

  assign MULW   = (opcode == 7'b0111011) & (funct3 == 3'b000) & (funct7 == 7'b0000001);
  assign DIVW   = (opcode == 7'b0111011) & (funct3 == 3'b100) & (funct7 == 7'b0000001);
  assign DIVUW  = (opcode == 7'b0111011) & (funct3 == 3'b101) & (funct7 == 7'b0000001);
  assign REMW   = (opcode == 7'b0111011) & (funct3 == 3'b110) & (funct7 == 7'b0000001);
  assign REMUW  = (opcode == 7'b0111011) & (funct3 == 3'b111) & (funct7 == 7'b0000001);

  always @(*) begin
    if(EBREAK)
      ebreak();
  end

  wire load, mdr;
  assign load = LB | LH | LW | LBU | LHU | LWU | LD;
  assign mdr  = MUL | MULH | MULHSU | MULHU | DIV | DIVU | REM | REMU | MULW | DIVW | DIVUW | REMW | REMUW;

  /* inst type */
  wire instR, instI, instS, instB, instU, instJ;
  wire [5:0] inst_type;
  assign inst_type = {instR, instI, instS, instB, instU, instJ};
  assign instR = ADD | SUB | SLL | SLT | SLTU | XOR | SRL | SRA | OR | AND | ADDW | SUBW | SLLW | SRLW | SRAW | mdr;
  assign instI = JALR | load | ADDI | SLTI | SLTIU | XORI | ORI | ANDI | SLLI | SRLI | SRAI | EBREAK | ADDIW | SLLIW | SRLIW | SRAIW;
  assign instS = SB | SH | SW | SD;
  assign instB = BEQ | BNE | BLT | BGE | BLTU | BGEU;
  assign instU = AUIPC | LUI;
  assign instJ = JAL;

  /* register write back enable & source */
  wire rf_write_src_pc, rf_write_src_mem;
  wire [1:0] rf_write_src_type;
  assign rf_write_src_type  = {rf_write_src_pc, rf_write_src_mem};
  assign rf_write_src_pc    = JAL | JALR;
  assign rf_write_src_mem   = load;
  assign RegWen   = LUI | AUIPC | JAL | JALR | load | ADDI | SLTI | SLTIU | XORI | ORI | ANDI | SLLI | SRLI | SRAI |
    ADD | SUB | SLL | SLT | SLTU | XOR | SRL | SRA | OR | AND | ADDIW | SLLIW | SRLIW | SRAIW | ADDW | SUBW | SLLW | SRLW | SRAW | mdr;
  MuxKeyWithDefault #(2, 2, 2) rf_write_src_encoder(rf_write_src_out, rf_write_src_type, 2'b00, {
    2'b10, 2'b01,
    2'b01, 2'b10
  });

  /* branch type */
  wire [6:0] branch_type;
  wire JUMP;
  assign JUMP = JAL | JALR;
  assign branch_type = {BEQ, BNE, BGE, BLT, BGEU, BLTU, JUMP};
  MuxKeyWithDefault #(7, 7, 3) branch_encoder (Branch, branch_type, `BRANCH_NO, {
    7'b1000000, `BRANCH_BEQ,
    7'b0100000, `BRANCH_BNE,
    7'b0010000, `BRANCH_BGE,
    7'b0001000, `BRANCH_BLT,
    7'b0000100, `BRANCH_BGEU,
    7'b0000010, `BRANCH_BLTU,
    7'b0000001, `BRANCH_J
  });


  /* alu source */
  wire [1:0] a_in, b_in;
  wire a_in_rs1, a_in_pc;
  wire b_in_rs2, b_in_imm;
  assign a_in = {a_in_rs1, a_in_pc};
  assign b_in = {b_in_rs2, b_in_imm};
  assign a_in_rs1 = ADDI | JALR | instB | load | instS | ADDI | SLTI | SLTIU | XORI | ORI | ANDI | SLLI | SRLI | SRAI | instR | ADDIW | SLLIW | SRLIW | SRAIW;
  assign a_in_pc  = AUIPC | JAL;
  assign b_in_rs2 = instB | instR;
  assign b_in_imm = ADDI | AUIPC | LUI | JAL | JALR | load | instS | ADDI | SLTI | SLTIU | XORI | ORI | ANDI | SLLI | SRLI | SRAI | ADDIW | SLLIW | SRLIW | SRAIW;

  //alu bus a
  MuxKey #(2, 2, 3) alu_a_encoder (ALUAsrc, {a_in_pc, a_in_rs1}, {
    2'b01, 3'b000,
    2'b10, 3'b001   
  });

  //alu bus b
  MuxKey #(2, 2, 3) alu_b_encoder (ALUBsrc, {b_in_imm,b_in_rs2}, {
    2'b01, 3'b000,
    2'b10, 3'b001 
  });

  //aluop out
  wire alu_add, alu_sub, alu_srl, alu_sra, alu_sll, alu_xor, alu_or, alu_and;
  wire alu_addw, alu_subw, alu_srlw, alu_sraw, alu_sllw, alu_slt, alu_sltu, alu_bout;

  assign alu_add  = AUIPC | JAL | JALR | load | instS | ADDI | ADD;
  assign alu_sub  = instB | SUB;
  assign alu_srl  = SRLI | SRL;
  assign alu_sra  = SRAI | SRA;
  assign alu_sll  = SLLI | SLL;
  assign alu_xor  = XORI | XOR;
  assign alu_or   = ORI | OR;
  assign alu_and  = ANDI | AND;
  assign alu_addw = ADDIW | ADDW;
  assign alu_subw = SUBW;
  assign alu_srlw = SRLIW | SRLW;
  assign alu_sraw = SRAIW | SRAW;
  assign alu_sllw = SLLIW | SLLW;
  assign alu_slt  = SLTI | SLT;
  assign alu_sltu = SLTIU | SLTU;
  assign alu_bout = LUI;

  wire [15:0] alu_func;
  assign alu_func = {alu_add, alu_sub, alu_srl, alu_sra, alu_sll, alu_xor, alu_or, alu_and,
    alu_addw, alu_subw, alu_srlw, alu_sraw, alu_sllw, alu_slt, alu_sltu, alu_bout};

  MuxKey #(16, 16, 4) alu_op_encoder(ALUop, alu_func, {
    16'h8000, `ALU_OP_ADD,
    16'h4000, `ALU_OP_SUB,
    16'h2000, `ALU_OP_SRL,
    16'h1000, `ALU_OP_SRA,
    16'h0800, `ALU_OP_SLL,
    16'h0400, `ALU_OP_XOR,
    16'h0200, `ALU_OP_OR,
    16'h0100, `ALU_OP_AND,
    16'h0080, `ALU_OP_ADDW,
    16'h0040, `ALU_OP_SUBW,
    16'h0020, `ALU_OP_SRLW,
    16'h0010, `ALU_OP_SRAW,
    16'h0008, `ALU_OP_SLLW,
    16'h0004, `ALU_OP_SLT,
    16'h0002, `ALU_OP_SLTU,
    16'h0001, `ALU_OP_BOUT
  });

  wire [12:0] mdr_type = {MUL, MULH, MULHSU, MULHU, DIV, DIVU, REM, REMU, MULW, DIVW, DIVUW, REMW, REMUW};

  MuxKeyWithDefault #(13, 13, 4) mdu_op_encoder(MDUop, mdr_type, 4'b0, {
    13'h1000, `MDU_OP_MUL   ,
    13'h0800, `MDU_OP_MULH  ,
    13'h0400, `MDU_OP_MULHSU, 
    13'h0200, `MDU_OP_MULHU ,
    13'h0100, `MDU_OP_DIV   ,
    13'h0080, `MDU_OP_DIVU  ,
    13'h0040, `MDU_OP_REM   ,
    13'h0020, `MDU_OP_REMU  ,
    13'h0010, `MDU_OP_MULW  ,
    13'h0008, `MDU_OP_DIVW  ,
    13'h0004, `MDU_OP_DIVUW ,
    13'h0002, `MDU_OP_REMW  ,
    13'h0001, `MDU_OP_REMUW 
  });

  //imm out
  MuxKey #(5, 6, 64) imm_selector (imm, inst_type, {
    6'b010000, immI,
    6'b001000, immS,
    6'b000100, immB,
    6'b000010, immU,
    6'b000001, immJ 
  });
  
  //memory
  assign MemRen = load;
  assign MemWen = instS;
  assign MemOp  = funct3;

endmodule